# -*- coding: utf-8 -*-
"""
Created on
@Time: 2025/1/13 15:33
@Author: Mr.Z
@contact: <QQ:40061980>
@contact: bob@pheks.com
@File: backup.py
@IDE: PyCharm
@Python Version：3.12
@Description: 文件备份工具UI界面
"""
from datetime import datetime
from random import choices
import ttkbootstrap as ttk
from ttkbootstrap.style import Bootstyle
from tkinter.filedialog import askdirectory
from ttkbootstrap.dialogs import Messagebox
from ttkbootstrap.constants import *
from tkinter.scrolledtext import ScrolledText
from pathlib import Path

PATH = Path(__file__).parent / 'assets/backup'  # 设置图片路径


class BackMeUp(ttk.Frame):

    def __init__(self, *args, **kwargs):
        """初始化"""
        super().__init__(*args, **kwargs)  # 父类初始化
        self.pack(fill=BOTH, expand=YES)  # 填充父容器

        image_files = {
            'properties-dark': 'icon-park-solid--setting-two-dark.png',
            'properties-light': 'icon-park-solid--setting-two-light.png',
            'add-to-backup-dark': 'tdesign--folder-add-filled-dark.png',
            'add-to-backup-light': 'tdesign--folder-add-filled.png',
            'stop-backup-dark': 'material-symbols--cancel-presentation-dark.png',
            'stop-backup-light': 'material-symbols--cancel-presentation.png',
            'play': 'flat-color-icons--start.png',
            'refresh': 'solar--refresh-square-bold.png',
            'stop-dark': 'gg--play-stop-r-dark.png',
            'stop-light': 'gg--play-stop-r.png',
            'opened-folder': 'material-symbols--folder-open.png',
            'logo': 'clarity--backup-solid-alerted.png'
        }

        self.photoimages = []  # 图片列表
        imgpath = Path(__file__).parent / 'assets/backup'  # 设置图片路径
        for key, val in image_files.items():
            # 加载图片
            _path = imgpath / val
            self.photoimages.append(ttk.PhotoImage(name=key, file=_path))

        # buttonbar
        buttonbar = ttk.Frame(self, style='primary.TFrame')
        buttonbar.pack(fill=X, pady=1, side=TOP)

        # 新建备份按钮
        _func = lambda: Messagebox.ok(message='添加新的备份任务')  # 弹出提示框
        # 按钮图标
        btn = ttk.Button(
            master=buttonbar, text='新建备份',
            image='add-to-backup-light',
            compound=LEFT,  # 图片左对齐
            command=_func  # 绑定事件
        )
        btn.pack(side=LEFT, ipadx=5, ipady=5, padx=(1, 0), pady=1)  # 按钮布局

        # 开始备份按钮
        _func = lambda: Messagebox.ok(message='开始备份...')
        btn = ttk.Button(
            master=buttonbar,  # 父容器
            text='备份',  # 按钮文字
            image='play',  # 按钮图标
            compound=LEFT,  # 图片左对齐
            command=_func  # 绑定事件
        )
        btn.pack(side=LEFT, ipadx=5, ipady=5, padx=0, pady=1)  # 按钮布局

        ## 刷新按钮
        _func = lambda: Messagebox.ok(message='Refreshing...')  # 弹出提示框
        btn = ttk.Button(
            master=buttonbar,
            text='刷新',
            image='refresh',
            compound=LEFT,
            command=_func
        )
        btn.pack(side=LEFT, ipadx=5, ipady=5, padx=0, pady=1)

        ## 停止备份按钮
        _func = lambda: Messagebox.ok(message='Stopping backup.')
        btn = ttk.Button(
            master=buttonbar,
            text='停止',
            image='stop-light',
            compound=LEFT,
            command=_func
        )
        btn.pack(side=LEFT, ipadx=5, ipady=5, padx=0, pady=1)

        ## 设置按钮
        _func = lambda: Messagebox.ok(message='Changing settings')
        btn = ttk.Button(
            master=buttonbar,
            text='设置',
            image='properties-light',
            compound=LEFT,
            command=_func
        )
        btn.pack(side=LEFT, ipadx=5, ipady=5, padx=0, pady=1)

        # 左侧面板
        left_panel = ttk.Frame(self, style='bg.TFrame')  # 左侧面板
        left_panel.pack(side=LEFT, fill=Y)  # 左侧面板布局

        # backup summary (collapsible)  备份摘要（可折叠）
        bus_cf = CollapsingFrame(left_panel)  # 创建折叠面板
        bus_cf.pack(fill=X, pady=1)  # 布局

        # container  容器
        bus_frm = ttk.Frame(bus_cf, padding=5)  # 创建容器
        bus_frm.columnconfigure(1, weight=1)  # 列权重
        bus_cf.add(
            child=bus_frm,
            title='备份概览',
            bootstyle=SECONDARY)

        # destination 目标路径
        lbl = ttk.Label(bus_frm, text='目标路径:')  # 标签
        lbl.grid(row=0, column=0, sticky=W, pady=2)  # 布局
        lbl = ttk.Label(bus_frm, textvariable='destination')  # 标签
        lbl.grid(row=0, column=1, sticky=EW, padx=5, pady=2)  # 布局
        self.setvar('destination', 'd:/test/')  # 设置变量

        # last run  最后运行
        lbl = ttk.Label(bus_frm, text='上次运行:')
        lbl.grid(row=1, column=0, sticky=W, pady=2)
        lbl = ttk.Label(bus_frm, textvariable='lastrun')
        lbl.grid(row=1, column=1, sticky=EW, padx=5, pady=2)
        self.setvar('lastrun', '14.06.2021 19:34:43')

        # files Identical  相同文件
        lbl = ttk.Label(bus_frm, text='相同文件:')
        lbl.grid(row=2, column=0, sticky=W, pady=2)
        lbl = ttk.Label(bus_frm, textvariable='filesidentical')
        lbl.grid(row=2, column=1, sticky=EW, padx=5, pady=2)
        self.setvar('filesidentical', '15%')

        # section separator  区分符
        sep = ttk.Separator(bus_frm, bootstyle=SECONDARY)
        sep.grid(row=3, column=0, columnspan=2, pady=10, sticky=EW)

        # properties button  按钮
        _func = lambda: Messagebox.ok(message='Changing properties')
        bus_prop_btn = ttk.Button(
            master=bus_frm,
            text='Properties',
            image='properties-dark',
            compound=LEFT,
            command=_func,
            bootstyle=LINK
        )
        bus_prop_btn.grid(row=4, column=0, columnspan=2, sticky=W)

        # add to backup button  添加到备份按钮
        _func = lambda: Messagebox.ok(message='Adding to backup')
        add_btn = ttk.Button(
            master=bus_frm,
            text='Add to backup',
            image='add-to-backup-dark',
            compound=LEFT,
            command=_func,
            bootstyle=LINK
        )
        add_btn.grid(row=5, column=0, columnspan=2, sticky=W)

        # backup status (collapsible)  备份状态（可折叠）
        status_cf = CollapsingFrame(left_panel)
        status_cf.pack(fill=BOTH, pady=1)

        # container
        status_frm = ttk.Frame(status_cf, padding=10)
        status_frm.columnconfigure(1, weight=1)
        status_cf.add(
            child=status_frm,
            title='备份状态',
            bootstyle=SECONDARY
        )
        # progress message  进度消息
        lbl = ttk.Label(
            master=status_frm,
            textvariable='prog-message',
            font='Helvetica 10 bold'
        )
        lbl.grid(row=0, column=0, columnspan=2, sticky=W)
        self.setvar('prog-message', '正在备份...')

        # progress bar  进度条
        pb = ttk.Progressbar(
            master=status_frm,
            variable='prog-value',
            bootstyle=SUCCESS
        )
        pb.grid(row=1, column=0, columnspan=2, sticky=EW, pady=(10, 5))
        self.setvar('prog-value', 71)

        # 开发时间
        lbl = ttk.Label(status_frm, textvariable='prog-time-started')
        lbl.grid(row=2, column=0, columnspan=2, sticky=EW, pady=2)
        self.setvar('prog-time-started', '开始时间: 14.06.2021 19:34:56')

        # 已用时间
        lbl = ttk.Label(status_frm, textvariable='prog-time-elapsed')
        lbl.grid(row=3, column=0, columnspan=2, sticky=EW, pady=2)
        self.setvar('prog-time-elapsed', '已用时间: 1 sec')

        # time remaining  剩余时间
        lbl = ttk.Label(status_frm, textvariable='prog-time-left')
        lbl.grid(row=4, column=0, columnspan=2, sticky=EW, pady=2)
        self.setvar('prog-time-left', '预计剩余: 0 sec')

        # section separator  区分符
        sep = ttk.Separator(status_frm, bootstyle=SECONDARY)
        sep.grid(row=5, column=0, columnspan=2, pady=10, sticky=EW)

        # stop button  停止按钮
        _func = lambda: Messagebox.ok(message='停止备份任务')
        btn = ttk.Button(
            master=status_frm,
            text='停止备份',
            image='stop-backup-dark',
            compound=LEFT,
            command=_func,
            bootstyle=LINK
        )
        btn.grid(row=6, column=0, columnspan=2, sticky=W)

        # section separator  区分符
        sep = ttk.Separator(status_frm, bootstyle=SECONDARY)
        sep.grid(row=7, column=0, columnspan=2, pady=10, sticky=EW)

        # current file message  当前文件消息
        lbl = ttk.Label(status_frm, textvariable='current-file-msg')
        lbl.grid(row=8, column=0, columnspan=2, pady=2, sticky=EW)
        self.setvar('current-file-msg', '正在上传文件: d:/test/settings.txt')

        # logo
        lbl = ttk.Label(left_panel, image='logo', style='bg.TLabel')
        lbl.pack(side='bottom')

        # right panel  右侧面板
        right_panel = ttk.Frame(self, padding=(2, 1))
        right_panel.pack(side=RIGHT, fill=BOTH, expand=YES)

        # file input  输入框
        browse_frm = ttk.Frame(right_panel)
        browse_frm.pack(side=TOP, fill=X, padx=2, pady=1)

        file_entry = ttk.Entry(browse_frm, textvariable='folder-path')
        file_entry.pack(side=LEFT, fill=X, expand=YES)

        btn = ttk.Button(
            master=browse_frm,
            image='opened-folder',
            bootstyle=(LINK, SECONDARY),
            command=self.get_directory
        )
        btn.pack(side=RIGHT)

        # Treeview  树视图
        tv = ttk.Treeview(right_panel, show='headings', height=5)  # 创建树视图
        # 定义列
        tv.configure(columns=(
            '名称', '状态', '上次修改时间',
            '上次运行时间', '大小'
        ))
        tv.column('名称', width=150, stretch=True)

        for col in ['上次修改时间', '上次运行时间', '大小']:
            tv.column(col, stretch=False)

        for col in tv['columns']:
            tv.heading(col, text=col.title(), anchor=W)

        tv.pack(fill=X, pady=1)

        # scrolling text output  输出框
        scroll_cf = CollapsingFrame(right_panel)
        scroll_cf.pack(fill=BOTH, expand=YES)

        output_container = ttk.Frame(scroll_cf, padding=1)
        _value = '日志输出: 正在备份... [正在上传文件: D:/sample_file_35.txt]'
        self.setvar('scroll-message', _value)
        st = ScrolledText(output_container)
        st.pack(fill=BOTH, expand=YES)
        scroll_cf.add(output_container, textvariable='scroll-message')

        # seed with some sample data

        # starting sample directory  开始样例目录
        file_entry.insert(END, 'D:/text/myfiles/top-secret/samples/')

        # treeview and backup logs  树视图和备份日志
        for x in range(20, 35):
            result = choices(['Backup Up', 'Missed in Destination'])[0]
            st.insert(END, f'19:34:{x}\t\t Uploading: D:/file_{x}.txt\n')
            st.insert(END, f'19:34:{x}\t\t Upload {result}.\n')
            timestamp = datetime.now().strftime('%d.%m.%Y %H:%M:%S')
            tv.insert('', END, x,
                      values=(f'sample_file_{x}.txt',
                              result, timestamp, timestamp,
                              f'{int(x // 3)} MB')
                      )
        tv.selection_set(20)

    def get_directory(self):
        """打开对话框以获取目录并更新变量"""
        self.update_idletasks()
        d = askdirectory()
        if d:
            self.setvar('folder-path', d)


class CollapsingFrame(ttk.Frame):
    """可折叠的框架，可通过单击打开或关闭。"""

    def __init__(self, master, **kwargs):
        super().__init__(master, **kwargs)
        self.columnconfigure(0, weight=1)
        self.cumulative_rows = 0

        # widget images
        self.images = [
            ttk.PhotoImage(file=PATH / 'ri--arrow-up-double-line.png'),
            ttk.PhotoImage(file=PATH / 'vaadin--angle-double-right.png')
        ]

    def add(self, child, title="", bootstyle=PRIMARY, **kwargs):
        """Add a child to the collapsible frame

        Parameters:

            child (Frame):
                The child frame to add to the widget.

            title (str):
                The title appearing on the collapsible section header.

            bootstyle (str):
                The style to apply to the collapsible section header.

            **kwargs (Dict):
                Other optional keyword arguments.
        """
        if child.winfo_class() != 'TFrame':
            return

        style_color = Bootstyle.ttkstyle_widget_color(bootstyle)
        frm = ttk.Frame(self, bootstyle=style_color)
        frm.grid(row=self.cumulative_rows, column=0, sticky=EW)

        # header title
        header = ttk.Label(
            master=frm,
            text=title,
            bootstyle=(style_color, INVERSE)
        )
        if kwargs.get('textvariable'):
            header.configure(textvariable=kwargs.get('textvariable'))
        header.pack(side=LEFT, fill=BOTH, padx=10)

        # header toggle button  头部切换按钮
        def _func(c=child):
            return self._toggle_open_close(c)

        btn = ttk.Button(
            master=frm,
            image=self.images[0],
            bootstyle=style_color,
            command=_func
        )
        btn.pack(side=RIGHT)

        # assign toggle button to child so that it can be toggled
        child.btn = btn
        child.grid(row=self.cumulative_rows + 1, column=0, sticky=NSEW)

        # increment the row assignment
        self.cumulative_rows += 2

    def _toggle_open_close(self, child):
        """Open or close the section and change the toggle button
        image accordingly.

        Parameters:

            child (Frame):
                The child element to add or remove from grid manager.
        """
        if child.winfo_viewable():
            child.grid_remove()
            child.btn.configure(image=self.images[1])
        else:
            child.grid()
            child.btn.configure(image=self.images[0])


if __name__ == '__main__':
    app = ttk.Window("备份工具")  # 创建窗口并设置标题
    # 设置窗口图标
    app.iconbitmap(PATH / 'solar--refresh-square-bold.ico')

    # 获取屏幕尺寸和窗口尺寸
    screen_width = app.winfo_screenwidth()
    screen_height = app.winfo_screenheight()
    window_width = 1300  # 窗口宽度
    window_height = 900  # 窗口高度

    # 计算窗口居中位置
    x = (screen_width // 2) - (window_width // 2)
    y = (screen_height // 2) - (window_height // 2)

    # 设置窗口位置
    app.geometry(f'{window_width}x{window_height}+{x}+{y}')

    BackMeUp(app)  # 创建备份工具界面
    app.mainloop()  # 进入消息循环
